package 面向对象;

●每个模块都有一个唯一的名称:    如： "package 面向对象"中的“面向对象”
●每个模块在源文件中都有一些描述：模块描述在名为Module的源文件中表示module-info.java应该这样命名。每个模块应该只有一个模块描述符（module-info.java)
                         模块描述符是一个Java文件。它不是XML、文本或属性文件。
●模块描述符文件放在顶层目录中：  顶层目录是模块的根文件夹。如,如果您要开发例如：eg.com.taman.mod1模块,则应将模块描述符放在例
                         如：eg.com.taman.mod1 模块目录。
●每个模块可以有任意数量的包和类型
●一个模块可以依赖于任意数量的模块

●模块描述符
在Java9模块中，模块描述符是包含描述模块的模块元数据的资源。它不是XML或属性文件，而是普通的Java文件。
必须将此文件命名为module-info.java并将其放在模块的根文件夹中。与其他Java源文件一样，模块文件被编译到模块中module-info类使用 javac 命令。

使用module关键字创建模块描述符：

module  {
  // Module Meta Data goes here.
}
例如：

module eg.com.taman.mod1 {

}
这是一个简单而最小的模块描述符示例。让我们讨论一下模块元数据。

▪模块元数据

模块包含以下基本元数据：

	▫唯一的名字
	▫出口条款
	▫requires子句

我将在下面的部分更深入地讨论并提供一些示例。

◇唯一的名字
模块具有唯一的名称。使用module关键字定义模块类型，如本例所示：

module eg.com.taman.mod1 {
}

◇出口条款
模块可以将其包导出到外部世界，以便其他模块可以使用它们。在模块描述符中，使用 exports 子句将包导出到外部世界或其他模块：

module eg.com.taman.mod1 {
   exports eg.com.taman.service;
}
请注意，并非强制导出所有包。由你决定导出哪一种。

◇requires子句
模块可以导入或使用其他模块包。在模块描述符中，使用requires子句导入其他模块以使用其包：

module eg.com.taman.mod2 {
   requires eg.com.taman.mod1;
}
正如你在这个例子中看到的， eg.com.taman.mod1 已导出 eg.com.taman.service，所以 eg.com.taman.mod2 要求mod1导入其导出的所有包，以便在其子
类型（类、枚举、接口等）中使用它们。

但是请记住， exports 关键字将包导出到其他模块，并且需要关键字 imports 模块才能在内部使用所有导出的包。在模块中定义但未导出的任何包都是私有封装的，永
远无法访问。

一个模块可以有超过这个数量的元数据，但这足以让您开始模块化编程。

现在，让我们确定一些关于模块描述符语法的要点。

●关于模块描述符的注意事项
在构建模块描述符之前，您应该记住以下要点：

▪模块描述符可以只包含模块名而不包含其他内容；不包含导出或requires子句。
▪模块描述符可以由一个或多个exports子句组成，而不包含requires子句；这意味着它将包导出到其他模块，但不依赖于任何其他模块—它是一个独立的模块。
▪模块描述符可以同时具有exports和requires子句；这意味着它将包导出到其他模块并使用其他模块的包—因为它依赖于其他模块，所以它不是一个独立的模块。
▪模块描述符可以有零个、一个或多个requires子句。
▪模块是从modulepath加载的（就像类是从类路径加载的一样）。

等等，你说。为什么我不能像以前那样使用类路径？

●为什么选择modulepath？
作为一名Java开发人员，您知道什么是类路径地狱：与Windows®编程中的DLL地狱类似，Java中的类路径地狱之所以出现，是因为您的程序不是一组固定的代码，而是JVM
在特定实例中加载的一组类。您的代码可能处于这样一种情况：由于解析规则，平台上的同一命令行会导致不同的行为。目录结构可能不同。标准库的版本可以不同，也可以隐
藏。因为Java支持第一次遇到的策略，所以未知的排序依赖关系可能会使代码变得混乱。

从Java9开始，您将跳入另一种地狱： modulepath地狱 。

类路径是用户定义并内置的一系列类和包或jar。JVM或Java编译器需要类路径来编译应用程序或类。

在Java9之前，编译器和运行时通过类路径定位类型：包含已编译Java类的文件夹和库存档文件的列表。它是由CLASSPATH环境变量、放在JRE特殊文件夹中的扩展以及提供给javac和java命令的选项的组合定义的。目标是减少应用程序启动时间。

因为类型可以从几个不同的位置加载，所以搜索这些位置的顺序会导致应用程序变得脆弱。

很多年前，我在我的系统上安装了一个来自第三方供应商的Java应用程序。该应用程序的安装程序将一个旧版本的Java库放入JRE的extensions文件夹中。我系统上的几个Java应用程序依赖于该库的更新版本，因为它包含了库的旧类型的附加类型和增强版本。

因为JRE的extensions文件夹中的类是在类路径上的其他类之前加载的，所以依赖于较新库版本的应用程序停止工作，在运行时出现 NoClassDefFoundErrors 和 NoSuchMethodErrors 失败，有时是在应用程序开始执行很久之后。

modulepath是一系列模块（以文件夹或JAR格式提供）。如果模块是文件夹格式，则表示该模块是分解模块格式。如果它是JAR格式的，那么这个JAR就是模块化JAR。

模块和模块描述符提供的可靠配置有助于消除许多此类运行时类路径问题。每个模块都显式地声明其依赖项，这些依赖项作为应用程序启动来解析。

一个模块，一个包
modulepath只能包含每个模块中的一个，并且每个包只能在一个模块中定义。如果两个或多个模块具有相同的名称或导出相同的包，则运行时将在运行程序之前立即终止。

总结
在本教程中，讲解了模块的基本定义、内容和配置；Java 9中封装的工作原理和接口的定义；如何列出可用的模块；Java 8和Java 9应用程序之间的区别；如何使用模块的基本规则；以及classpath和modulepath之间的区别（以及他们能制造的地狱）。

//public class 模块 {
//
//}
//从Java 9开始，JDK又引入了模块（Module）。
//
//什么是模块？这要从Java 9之前的版本说起。
//
//我们知道，.class文件是JVM看到的最小可执行文件，而一个大型程序需要编写很多Class，并生成一堆.class文件，很不便于管理，所以，jar文件就是class文件的容器。
//
//在Java 9之前，一个大型Java程序会生成自己的jar文件，同时引用依赖的第三方jar文件，而JVM自带的Java标准库，实际上也是以jar文件形式
//存放的，这个文件叫rt.jar，一共有60多M。
//
//如果是自己开发的程序，除了一个自己的app.jar以外，还需要一堆第三方的jar包，运行一个Java程序，一般来说，命令行写这样：
//
//java -cp app.jar:a.jar:b.jar:c.jar com.liaoxuefeng.sample.Main
// 注意：JVM自带的标准库rt.jar不要写到classpath中，写了反而会干扰JVM的正常运行。
//如果漏写了某个运行时需要用到的jar，那么在运行期极有可能抛出ClassNotFoundException。
//
//所以，jar只是用于存放class的容器，它并不关心class之间的依赖。
//
//从Java 9开始引入的模块，主要是为了解决“依赖”这个问题。如果a.jar必须依赖另一个b.jar才能运行，那我们应该给a.jar加点说明啥的，让程序在编译和运行的时候能自
//动定位到b.jar，这种自带“依赖关系”的class容器就是模块。
//
//为了表明Java模块化的决心，从Java 9开始，原有的Java标准库已经由一个单一巨大的rt.jar分拆成了几十个模块，这些模块以.jmod扩展名标识，可以
//在$JAVA_HOME/jmods目录下找到它们：
//
//java.base.jmod
//java.compiler.jmod
//java.datatransfer.jmod
//java.desktop.jmod
//...
//这些.jmod文件每一个都是一个模块，模块名就是文件名。例如：模块java.base对应的文件就是java.base.jmod。模块之间的依赖关系已经被写入到模块
//内的module-info.class文件了。所有的模块都直接或间接地依赖java.base模块，只有java.base模块不依赖任何模块，它可以被看作是“根模块”，好比所有
//的类都是从Object直接或间接继承而来。
//
//把一堆class封装为jar仅仅是一个打包的过程，而把一堆class封装为模块则不但需要打包，还需要写入依赖关系，并且还可以包含二进制代码（通常是JNI扩展）。此外，
//模块支持多版本，即在同一个模块中可以为不同的JVM提供不同的版本。
//
//编写模块
//那么，我们应该如何编写模块呢？还是以具体的例子来说。首先，创建模块和原有的创建Java项目是完全一样的，以oop-module工程为例，它的目录结构如下：
//
//oop-module
//├── bin
//├── build.sh
//└── src
//    ├── com
//    │   └── itranswarp
//    │       └── sample
//    │           ├── Greeting.java
//    │           └── Main.java
//    └── module-info.java
//其中，bin目录存放编译后的class文件，src目录存放源码，按包名的目录结构存放，仅仅在src目录下多了一个module-info.java这个文件，这就是模块的描述文件。
//在这个模块中，它长这样：
//
//module hello.world {
//	requires java.base; // 可不写，任何模块都会自动引入java.base
//	requires java.xml;
//}
//其中，module是关键字，后面的hello.world是模块的名称，它的命名规范与包一致。花括号的requires xxx;表示这个模块需要引用的其他模块名。除了java.base可
//以被自动引入外，这里我们引入了一个java.xml的模块。
//
//当我们使用模块声明了依赖关系后，才能使用引入的模块。例如，Main.java代码如下：
//
//package com.itranswarp.sample;
//
//// 必须引入java.xml模块后才能使用其中的类:
//import javax.xml.XMLConstants;
//
//public class Main {
//	public static void main(String[] args) {
//		Greeting g = new Greeting();
//		System.out.println(g.hello(XMLConstants.XML_NS_PREFIX));
//	}
//}
//如果把requires java.xml;从module-info.java中去掉，编译将报错。可见，模块的重要作用就是声明依赖关系。
//
//下面，我们用JDK提供的命令行工具来编译并创建模块。
//
//首先，我们把工作目录切换到oop-module，在当前目录下编译所有的.java文件，并存放到bin目录下，命令如下：
//
//$ javac -d bin src/module-info.java src/com/itranswarp/sample/*.java
//如果编译成功，现在项目结构如下：
//
//oop-module
//├── bin
//│   ├── com
//│   │   └── itranswarp
//│   │       └── sample
//│   │           ├── Greeting.class
//│   │           └── Main.class
//│   └── module-info.class
//└── src
//    ├── com
//    │   └── itranswarp
//    │       └── sample
//    │           ├── Greeting.java
//    │           └── Main.java
//    └── module-info.java
//注意到src目录下的module-info.java被编译到bin目录下的module-info.class。
//
//下一步，我们需要把bin目录下的所有class文件先打包成jar，在打包的时候，注意传入--main-class参数，让这个jar包能自己定位main方法所在的类：
//
//$ jar --create --file hello.jar --main-class com.itranswarp.sample.Main -C bin .
//现在我们就在当前目录下得到了hello.jar这个jar包，它和普通jar包并无区别，可以直接使用命令java -jar hello.jar来运行它。但是我们的目标是创建模块，
//所以，继续使用JDK自带的jmod命令把一个jar包转换成模块：
//
//$ jmod create --class-path hello.jar hello.jmod
//于是，在当前目录下我们又得到了hello.jmod这个模块文件，这就是最后打包出来的传说中的模块！
//
//运行模块
//要运行一个jar，我们使用java -jar xxx.jar命令。要运行一个模块，我们只需要指定模块名。试试：
//
//$ java --module-path hello.jmod --module hello.world
//结果是一个错误：
//
//Error occurred during initialization of boot layer
//java.lang.module.FindException: JMOD format not supported at execution time: hello.jmod
//原因是.jmod不能被放入--module-path中。换成.jar就没问题了：
//
//$ java --module-path hello.jar --module hello.world
//Hello, xml!
//那我们辛辛苦苦创建的hello.jmod有什么用？答案是我们可以用它来打包JRE。
//
//打包JRE
//前面讲了，为了支持模块化，Java 9首先带头把自己的一个巨大无比的rt.jar拆成了几十个.jmod模块，原因就是，运行Java程序的时候，实际上我们用到的JDK模块，
//并没有那么多。不需要的模块，完全可以删除。
//
//过去发布一个Java应用程序，要运行它，必须下载一个完整的JRE，再运行jar包。而完整的JRE块头很大，有100多M。怎么给JRE瘦身呢？
//
//现在，JRE自身的标准库已经分拆成了模块，只需要带上程序用到的模块，其他的模块就可以被裁剪掉。怎么裁剪JRE呢？并不是说把系统安装的JRE给删掉部分模块，
//而是“复制”一份JRE，但只带上用到的模块。为此，JDK提供了jlink命令来干这件事。命令如下：
//
//$ jlink --module-path hello.jmod --add-modules java.base,java.xml,hello.world --output jre/
//我们在--module-path参数指定了我们自己的模块hello.jmod，然后，在--add-modules参数中指定了我们用到的3个模块java.base、java.xml和hello.world，
//用,分隔。最后，在--output参数指定输出目录。
//
//现在，在当前目录下，我们可以找到jre目录，这是一个完整的并且带有我们自己hello.jmod模块的JRE。试试直接运行这个JRE：
//
//$ jre/bin/java --module hello.world
//Hello, xml!
//要分发我们自己的Java应用程序，只需要把这个jre目录打个包给对方发过去，对方直接运行上述命令即可，既不用下载安装JDK，也不用知道如何配置我们自己的模块，
//极大地方便了分发和部署。
//
//访问权限
//前面我们讲过，Java的class访问权限分为public、protected、private和默认的包访问权限。引入模块后，这些访问权限的规则就要稍微做些调整。
//
//确切地说，class的这些访问权限只在一个模块内有效，模块和模块之间，例如，a模块要访问b模块的某个class，必要条件是b模块明确地导出了可以访问的包。
//
//举个例子：我们编写的模块hello.world用到了模块java.xml的一个类javax.xml.XMLConstants，我们之所以能直接使用这个类，是因为
//模块java.xml的module-info.java中声明了若干导出：
//
//module java.xml {
//    exports java.xml;
//    exports javax.xml.catalog;
//    exports javax.xml.datatype;
//    ...
//}
//只有它声明的导出的包，外部代码才被允许访问。换句话说，如果外部代码想要访问我们的hello.world模块中的com.itranswarp.sample.Greeting类，我们必须将其导出：
//
//module hello.world {
//    exports com.itranswarp.sample;
//
//    requires java.base;
//	requires java.xml;
//}
//因此，模块进一步隔离了代码的访问权限


